#include "path.h"
#include <talker.h>
#include <network.h>
#include <term.h>
#include <playtesters.h>

inherit "/std/effect_shadow";

int do_talker_control(mixed *args, string pattern);
int do_chat(string message, string pattern, string verb);

int handle_status_command(string mode, class talker_args args);
int handle_verbose_command(string mode, class talker_args args);
int handle_history_command(string channel, class talker_args args);
int handle_colour_list(mixed *colour_args, class talker_args args);

int handle_colour_command(string channel, string new_colour, class talker_args args);
int handle_colour_on_off_command( string str_status, class talker_args args);
int handle_colour_toggle_command( class talker_args args);

int handle_echo_command( string mode, class talker_args args);

void init() {
    mixed args;
    object where;
    string channel;

    where = environment(player);
#ifdef DEBUG
    tell_creator("taffyd", "%O, %O, %O.\n", player, this_player(), where);
#endif

    if (!living(where)) {
        player->init();
        return;
    }

    args = arg();

    if (!classp(args)) {
        args = new(class talker_args, status : 0, channels : ({ "one" }),
            verbose : 1, local_echo : 0, colour : 0 );
        set_arg(args);
    }

    where->add_command("talker", player,
        ({ ON_PATTERN,
           VERBOSE_PATTERN,
           CHANNEL_PATTERN,
           DELETE_CHANNEL_PATTERN,
           HISTORY_PATTERN,
           HISTORY_CHANNEL_PATTERN,
           LIST_PATTERN,
           LIST_CHANNEL_PATTERN,
           SET_COLOUR_PATTERN,
           COLOUR_ON_OFF_PATTERN,
           COLOUR_LIST,
           TOGGLE_COLOUR_PATTERN,
           NEW_CHANNEL_PATTERN,
           ECHO_PATTERN,
           "delete <string'channel'> from <direct:object'talker'>",
           "help" }),
        (: do_talker_control($4, $5) :) );

    where->add_command("chat", player, ({ "<string'message'>",
        "into <direct:object>" }), (: do_chat($4[0], $5, $6) :) );

    /*
     * If they've picked up a talker that's not theirs...
     * nuke the channels.
     */
    if ( !( EFFECTS + "talker" )->valid( args->channels, where ) ) {
        args->channels = ({ "one" });
        set_arg(args);
    }

    foreach (channel in args->channels) {
#ifdef DEBUG
        tell_creator("taffyd", "Adding channel. %O.\n", channel);
#endif
        where->add_command(lower_case(channel), player,
            "<string'message'>", (: do_chat($4[0], $5, $6) :) );
    }

    player->init();
} /* init() */

int do_talker_control(mixed *args, string pattern) {
    class talker_args t_args;
    int position;
    string full_name;

#ifdef DEBUG
    tell_creator("taffyd", "Args to talker control is %O. Pattern is %O.\n",
        args, pattern);
#endif

    t_args = arg();

    switch (pattern) {
        case ON_PATTERN:
            return handle_status_command(args[0], t_args);
        case VERBOSE_PATTERN:
            return handle_verbose_command(args[0], t_args);
        case CHANNEL_PATTERN:
            if ( !t_args->status ) {
                tell_object(this_player(), "The eyes are asleep.\n");
                return 1;
            }

            tell_object(this_player(), "You may use the following channels:\n" +
                (string)( EFFECTS +"talker" )->channels() +
                "You are on channel(s) " +
                query_multiple_short( t_args->channels ) + ".\n" );
            return 1;
        break;

        case ECHO_PATTERN:
            return handle_echo_command( args[0], t_args );

        case "delete <string'channel'> from <direct:object'talker'>":
            add_succeeded_mess( ({ "", "" }) );

        case DELETE_CHANNEL_PATTERN:
            if ( !t_args->status ) {
                tell_object(this_player(),  "The eyes are asleep.\n");
                return 1;
            }

            full_name = (EFFECTS + "talker")->normalise_name( args[ 0 ] );

            if ( !(EFFECTS +"talker")->valid( full_name ) ) {
                tell_object(this_player(), "The eyes are not aware of that "
                    "channel.\n");
                return 1;
            }

            position = member_array(full_name, t_args->channels);

            if (position == -1) {
                tell_object(this_player(), "The eyes are not listening to "
                    "that channel.\n");
                return 1;
            }

            if (sizeof(t_args->channels) > 1) {
                t_args->channels = t_args->channels[0..position-1] +
                    t_args->channels[position+1..];
                tell_object(this_player(), "The eyes sigh with relief as they "
                    "can now ignore what happens on "
                    "channel \"" + full_name + "\".\n");
            }
            else {
                tell_object(this_player(), "The eyes are bored with not listening "
                    "to any channel at all, and add channel \"one\" to their "
                    "list again.\n");
                t_args->channels = ({ "one" });
            }

            set_arg(t_args);
        break;

        case NEW_CHANNEL_PATTERN:
            if ( !t_args->status ) {
                tell_object(this_player(),  "The eyes are asleep.\n");
                return 1;
            }
            full_name = (EFFECTS + "talker")->normalise_name( args[ 0 ] );

            (EFFECTS+"talker")->clear_cache(this_player()->query_name());
            if ( ( EFFECTS +"talker" )->valid( full_name ) ) {
                if ( member_array( full_name, t_args->channels ) != -1 ) {
                    tell_object(this_player(), "The eyes frown at you; they are "
                        "already listening to that channel.\n");
                    return 1;
                }

                tell_object(this_player(),  "You whisper to " +
                    player->the_short() + " and the eyes add "
                    "channel \""+ full_name +"\" to the list.\n");
                t_args->channels += ({ full_name });
                set_arg(t_args);
                this_object()->init();
            }
            else {
                tell_object(this_player(), "You whisper to " +
                    player->the_short() + ", but the eyes "
                    "refuse to give you that channel.\n");
                return 1;
            }
        break;

        case SET_COLOUR_PATTERN:
        case SET_COLOUR_FULL_PATTERN:
            args[ 0 ] = (EFFECTS+"talker")->normalise_name( args[0] );
            return handle_colour_command( args[0], args[1], t_args );

        case COLOUR_ON_OFF_PATTERN:
            return handle_colour_on_off_command( args[0], t_args );

        case TOGGLE_COLOUR_PATTERN:
            return handle_colour_toggle_command(t_args);

        case COLOUR_LIST:
            return handle_colour_list( args, t_args );
        break;

        case HISTORY_CHANNEL_PATTERN:
            args[ 0 ] = (EFFECTS+"talker")->normalise_name( args[0] );
            return handle_history_command(args[0], t_args);

        case HISTORY_PATTERN:
            return handle_history_command("one", t_args);

        case LIST_PATTERN:
        case LIST_CHANNEL_PATTERN:

            if ( !t_args->status ) {
                tell_object(this_player(),  "The eyes are asleep.\n");
                return 1;
            }

//            add_succeeded_mess("");

            if (sizeof(args) == 1) {
                args[ 0 ] = (EFFECTS + "talker")->normalise_name( args[ 0 ] );
                tell_object(this_player(), "The following people are using "
                    "channel \""+ args[0] + "\":\n" +
                    (string)( EFFECTS +"talker" )->list( args[0] ) );
            }
            else
                tell_object(this_player(), "The following people are using "
                    "talkers:\n" + (string)( EFFECTS +"talker" )->list() );

            return 1;
        break;

        case "help":
        default:
            write( "Talker commands:\n\n"
                "help               : give this help file\n"
                "on|off             : switch talker on or off\n"
                "brief              : make talker use shorter messages\n"
                "verbose            : make talker use longer messages\n"
                "channels           : list valid channels\n"
                "<channel>          : switch to <channel> if valid\n"
                "delete <channel>   : switch <channel> off if valid\n"
                "list [channel]     : list users of the talker\n"
                "history [channel]  : show chat history of <channel>\n"
                "Prefix commands with \"talker\" to use, e.g. "
                "\"talker help\".  For a more detailed description type "
                "\"help talker\".\n" );
        break;
    }

    return 1;
} /* do_talker_control() */

int handle_history_command(string channel, class talker_args args) {
    mixed *history;
    string result;

    if (!args->status) {
        tell_object(this_player(), "The eyes are asleep.\n");
        return 1;
    }

    if (!channel)
        channel = "one";

    if ( !( EFFECTS +"talker" )->valid( channel ) ) {
        tell_object(this_player(), "The eyes are not aware of that "
            "channel.\n");
        return 1;
    }

//    add_succeeded_mess("");

    tell_object(this_player(), "You whisper to " + player->the_short() +
            ", asking for the last few chats on channel " + channel + ".\n");

    history = HIST->query_chat_history(lower_case( channel ) );

    if ( !arrayp( history ) || !sizeof( history ) ) {
        tell_object(this_player(), "The eyes mutter that nothing has "
            "been said on this channel, or it is not being remembered.\n" );
        return 1;
    }

    result = "$P$Channel " + channel + "$P$The eyes mutter about your bad "
        "memory.\n";

    result += implode(map(history, (: "$I$5=$C$" + $1[0] + $1[1] +
            "%^RESET%^" :)), "\n");
    tell_object(this_player(), result);
    return 1;
} /* handle_history_command() */

string query_colour_status( class talker_args args ) {
    if ( !mappingp( args->colour ) ) {
        return "off";
    }

    return args->colour[ "status" ];
} /* query_colour_status() */

int set_colour_status( class talker_args args, string status ) {
    if ( !args->colour ) {
        args->colour = DEFAULT_COLOUR_SETTINGS;
    }
    else {
        if ( stringp( args->colour ) ) {
            args->colour = DEFAULT_COLOUR_WITH( args->colour );
        }
    }

    args->colour[ "status" ] = status;
    set_arg( args );
    return 1;
} /* set_colour_status() */

int set_channel_colour( class talker_args args, string channel, string colour ) {

    if ( !channel || !colour )
        return 0;

    if ( !mappingp( args->colour ) ) {
        args->colour = DEFAULT_COLOUR_WITH(colour);
    }

    args->colour[ channel ] = colour;
    set_arg( args );
    return 1;
} /* set_channel_colour() */

varargs string query_channel_colour( class talker_args args, string channel ) {
    string colour;

    if ( !mappingp( args ) ) {
        return DEFAULT_COLOUR;
    }

    if ( colour = args->colour[ channel ] ) {
        return colour;
    }
    else {
        if ( colour = args->colour[ "default" ] ) {
            return colour;
        }
    }

    return DEFAULT_COLOUR;
} /* query_channel_colour() */

int handle_colour_toggle_command( class talker_args args ) {
    if ( query_colour_status( args ) == "on" ) {
        return handle_colour_on_off_command( "off", args );
    }

    return handle_colour_on_off_command( "on", args );
} /* handle_colour_toggle_command() */

int handle_colour_on_off_command( string str_status, class talker_args args ) {
    string status;

    status = query_colour_status( args );

    switch( str_status ) {
        case "on":
            if ( status == "on" ) {
                add_failed_mess( "Talker colours for $D are already on!\n" );
                return -1;
            }
            set_colour_status( args, "on" );
            add_succeeded_mess( ({ "The eyes on $D flash with " +
                query_channel_colour( args, "default" ) + "colour%^RESET%^.\n", "" }) );
        break;

        case "off":
            if ( status == "off" ) {
                add_failed_mess( "Talker colours for $D have already been turned off!\n" );
                return -1;
            }
            set_colour_status( args, "off" );
            add_succeeded_mess( ({ "The eyes on $D blink sadly.\n", "" }) );
        break;

        default:
            args->colour = DEFAULT_COLOUR_SETTINGS;
            set_arg( args );
            add_succeeded_mess( ({ "Talker colour settings for $D have been reset.\n", "" }) );
    }

    return 1;
} /* handle_colour_on_off_command() */

mapping create_valid_colours( string my_colours ) {
    string *colours;
    string *valid_colours = TERM_HANDLER->query_colour_codes();


    colours = map( explode( my_colours, " " ),
        (: upper_case( $1 ) :) );

    return unique_mapping( colours,
        (: member_array( $1, $(valid_colours) ) > -1 :) );
} /* create_valid_colours() */

int handle_colour_command(string channel, string new_colour, class talker_args args) {
    mapping colours;
    string valid_colour;

    if ( !(EFFECTS + "talker")->valid( channel ) && channel != "default" ) {
        tell_object( this_player(), "You cannot set that "
            "channel! Use the channel name \"default\" to set "
            "the default channel colour.\n" );
        return 1;
    }

    colours = create_valid_colours( new_colour );

   switch( sizeof( colours[ 0 ] ) ) {
        case 0:
            valid_colour = implode( map( colours[ 1 ], (: "%^" + $1 + "%^" :) ), "" );

            set_channel_colour( args, channel, valid_colour );
            add_succeeded_mess( ({ "The eyes flash with" + valid_colour +
                " colour.%^RESET%^\n", "" }) );
            return 1;

        case 1:
            add_failed_mess( "The colour $I is invalid.\n", colours[ 0 ] );
            return -1;

        default:
            add_failed_mess( "The colours $I are invalid.\n", colours[ 0 ] );
            return -1;
    }
} /* handle_colour_command() */

int handle_status_command(string mode, class talker_args args) {
    switch (mode) {
        case "on" :
            if ( args->status ) {
                tell_object(this_player(),  "The eyes are already alert "
                    "and listening to your every word.\n");
            }
            else {
                tell_object(this_player(), "The eyes open and light up "
                    "eagerly, then dim to a dull glow.\n");
                    args->status = 1;
            }
        break;

        case "off" :
            if ( !args->status ) {
                tell_object(this_player(), "You notice that the eyes are "
                    "already closed, so you wake them up again "
                    "in order to tell them to go to sleep.\n");
            } else {
                tell_object(this_player(), "The eyes slowly lose their "
                    "glow as they close.\n");
                    args->status = 0;
            }
        break;
    }

    set_arg(args);
    return 1;
} /* handle_status_command() */

int handle_verbose_command(string mode, class talker_args args) {

    if (!args->status) {
        tell_object(this_player(), "The eyes are asleep.\n");
        return 1;
    }

    switch (mode) {
        case "brief":
            if ( !args->verbose ) {
                tell_object(this_player(), "The eyes ignore you completely.\n");
            }
            else {
                tell_object(this_player(), "The eyes seem sad as they dim "
                    "slightly.\n");
                args->verbose = 0;
            }
        break;

        case "verbose":
            if ( args->verbose ) {
                tell_object(this_player(),  "The eyes glitter at you "
                    "angrily.  They already know that they are to "
                    "be verbose.\n");
            }
            else {
                tell_object(this_player(), "The eyes light up happily for "
                    "a moment.\n");
                args->verbose = 1;
            }
        break;
    }
    set_arg(args);
    return 1;
} /* handle_verbose_command() */

void receive( string channel, string sender, string text ) {
    class talker_args args;
    string *ignoring;
    object where;
    string colour;

    if ( !player || !environment( player ) )
        return;

    where = environment(player);
    args = arg();

     if (args && !classp(args))
       return;
    if (!args || !args->status || !living(where))
        return;

    /*
     * If we're not listening to this channel, then don't receive the
     * message.
     */
    if (member_array(channel, map(args->channels,
        (: lower_case($1) :)) ) == -1)
        return;

    /*
     * Creators can their talker messages through the dwchat command,
     * they don't need to hear it twice.
     */
    if (channel == "intermud" && creatorp(where) )
        return;

    if ( file_name( previous_object( 1 ) ) == "/secure/cmds/creator/exe_c" ) {
        if ( this_player(1) && creatorp(this_player(1)) &&
            !this_player(1)->query_property( "talker cheat" ) ) {
                user_event( "inform", this_player(1)->query_name() +
              " illegally attempted make " + sender + " chat \"" +
        text + "\" on channel " + channel, "cheat" );
            this_player(1)->add_property( "talker cheat", 1, 3 );

            return;
        }
    }
    ignoring = where->query_property("ignoring");

    if (ignoring && member_array(lower_case(sender), ignoring) != -1 )
        return;

    if ( args->verbose ) {
        text = ( channel == "one" ? "" : "["+ channel +"] " ) +
            sender +"'s wispy voice comes from " +
            player->the_short()+", saying: "+ text;
    }
    else {
        text = ( channel == "one" ? "" : "["+ channel +"] " ) +
            sender +" wisps: " +text;
    }

    if ( stringp( args->colour ) ) {
        colour = args->colour;
    }

    if ( mappingp( args->colour ) && query_colour_status( args ) == "on" ) {
        if ( !( colour = args->colour[ channel ] ) ) {
            if ( !(colour = args->colour[ (EFFECTS + "talker")->normalise_name( channel ) ] ) ) {
                colour = args->colour[ "default" ];
                /* Incase... */

                if ( !colour ) {
                    colour = DEFAULT_COLOUR;
                }
            }
        }
    }
    else {
        colour = "";
    }

    tell_object( where, colour +  "$I$5=$C$"+ text + "%^RESET%^\n" );
} /* receive() */

int do_chat(string message, string pattern, string verb) {
    class talker_args args;
    object *things;
    string sender;
#ifdef DEBUG
    tell_creator("taffyd", "Message is %s, pattern is %s, verb is "
        "%s.\n", message, pattern, verb);
#endif

    if (pattern == "into <direct:object>") {
        add_succeeded_mess("$N whisper$s lovingly to $D.\n");
        return 1;
    }

    args = arg();
    if (!args->status) {
        tell_object(this_player(), "The eyes are asleep.\n");
        return 1;
    }

    /*
     * Strip colours... makes sure players don't "embed" colour
     * codes.
     */
    while (strsrch(message, "%^") != -1) {
        message = replace(message, ({ "%^", "" }) );
    }

    /*
     * If we're not actually listening to this channel, then stop.
     */
    if (member_array( verb, map(args->channels,
        (: lower_case($1) :)) ) == -1 && verb != "chat") {
            return 0;
    }

    if (this_player()->query_property("gagged")) {
        add_failed_mess("You have been gagged!  You cannot chat until "
            "your gag is removed.  Perhaps you should talk to a creator "
            "about this.\n");
        return 0;
    }

    if (environment(player) != this_player()) {
        add_failed_mess("You're too far away from $D to whisper into it.\n");
        return 0;
    }

    if (verb == "chat") {
        if (member_array("one", args->channels) == -1) {
            verb = args->channels[0];
        }
        else {
            verb = "one";
        }
    }

    sender = this_player()->query_cap_name();

    /* Strip bell characters (*/

    message = replace( message, sprintf( "%c", 7 ), "!" );
    things = children( PATH + "talker" );

    if ( args->local_echo ) {
        things -= ({ find_object( PATH + "talker" ) });
    }
    else {
        things -= ({ this_object(), find_object( PATH + "talker" ) });
    }

    things->receive( verb, sender, message );

    if ( verb == "intermud" ) {
        /*
         * Rudimentary support for Intermud emotes.  Not supported on
         * the receiving end.
         */
        if (message[0] == '@' || message[0] == ':') {
            event( users(), "intermud_tell", sender + " ", message[1..], "dwchat" );
            message = "$N " + message[1..];
            SERVICES_D->eventSendChannel(sender, "discworld-chat", message, 1);
        }
        else {
            /* Intermud "discworld_chat" channel support for players. */
            SERVICES_D->eventSendChannel( sender, "discworld-chat", message, 0 );
            event( users(), "intermud_tell", sender +": ", message, "dwchat" );
        }
    }
    else {
        if ( verb == "playtesters" ) {
            if ( !playtesterp(TP) ) {
                args->channels -= ({ "playtesters" });
                set_arg(args);
                add_failed_mess("You're no longer a playtester!\n");
                return 0;
            }
        }

        HIST->add_chat_history( verb , sender +" wisped: ", message );
    }

    tell_object(this_player(), "You whisper into " +
        player->the_short() + ".\n");

    return 1;
} /* do_chat() */

/**
 * @ignore yes
 * Tape did this.
 */
void remove_effect_shadow( int i ) {
    if ( i == id ) {
        if ( player && environment( player ) ) {
            // This removes the add_command()s set up by the
            // shadow (this_object()) from the guy who's
            // carrying the talker.  It's a bit hacky, but I
            // can't think of any other way to do it.
            environment( player )->remove_object2( player, 1 );
        }
    }

    ::remove_effect_shadow( i );
} /* remove_effect_shadow() */

/**
 * @ignore yes
 */
protected string format_channel_colour( string colour ) {
    return colour + lower_case( replace_string(
        implode( explode( colour, "%^%^" ), " " ), "%^", "" ) ) + "%^RESET%^";
} /* format_channel_colour() */


int handle_colour_list(mixed *colour_args, class talker_args args) {
    string channel;

    if (!mappingp(args->colour) || query_colour_status( args ) == "off" ) {
        add_failed_mess( "You currently have talker colours disabled.  Use "
            "\"talker colour on\" to turn them on.\n" );
        return 0;
    }

    foreach( channel in args->channels ) {
        if ( args->colour[ channel ] ) {
            tell_object( this_player(),
                "Channel " + channel + " is " + format_channel_colour(
                args->colour[ channel ] ) + ".\n");
        }
    }

    if ( args->colour[ "default" ] ) {
        tell_object( this_player(),
            "The default talker colour is " + format_channel_colour(
            args->colour[ "default" ] ) + ".\n");
    }

    return 1;
} /* handle_colour_list() */

int handle_echo_command( string mode, class talker_args args) {
    int current;

    current = args->local_echo;

    if ( current ) {
        if ( mode == "on" ) {
            add_failed_mess( "Talker echo is already on for $D!\n" );
            return -1;
        }

        args->local_echo = 0;
        add_succeeded_mess( ({ "The eyes on $D flash slowly.\n", "" }) );
    }
    else {
        if ( mode == "off" ) {
            add_failed_mess( "Talker echo is already off for $D!\n" );
            return -1;
        }

        args->local_echo = 1;
        add_succeeded_mess( ({ "The eyes on $D flash twice rapidly.\n",
            "" }) );
    }

    set_arg( args );
    return 1;
} /* handle_echo_command() */
